<?php

namespace EthanZ\HyperfExt\Aspect;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Hyperf\Amqp\Producer;
use Hyperf\Di\Aop\AroundInterface;
use Hyperf\Di\Aop\ProceedingJoinPoint;
use Hyperf\Framework\Bootstrap\WorkerStartCallback;
use Hyperf\JsonRpc\DataFormatter;
use Hyperf\Rpc\Context;
use Hyperf\RpcClient\AbstractServiceClient;
use Hyperf\RpcClient\Client;
use Hyperf\Tracer\SpanStarter;
use Hyperf\Tracer\SpanTagManager;
use Hyperf\Tracer\SwitchManager;
use OpenTracing\Tracer;
use Psr\Container\ContainerInterface;
use EthanZ\HyperfExt\Common\TraceEntity;
use Hyperf\Di\Exception\Exception;
use const OpenTracing\Formats\TEXT_MAP;
use Hyperf\JsonRpc\TcpServer;
use Hyperf\JsonRpc\HttpServer;
use Elasticsearch\Client as ESClient;

class JsonRpcAspect implements AroundInterface
{

    use SpanStarter;


    public $classes = [
        AbstractServiceClient::class . '::__generateRpcPath',
        AbstractServiceClient::class . '::__generateData',
        Client::class . '::send',
        TcpServer::class . '::buildJsonRpcRequest',
        HttpServer::class . '::initRequestAndResponse',
        WorkerStartCallback::class . '::onWorkerStart',
        DataFormatter::class . "::formatResponse",
        Producer::class . "::produce",
        ESClient::class . '::performRequest',
    ];


    /**
     * @var ContainerInterface
     */
    private $container;

    /**
     * @var Tracer
     */
    private $tracer;

    /**
     * @var SwitchManager
     */
    private $switchManager;

    /**
     * @var SpanTagManager
     */
    private $spanTagManager;

    /**
     * @var Context
     */
    private $context;


    public function __construct(ContainerInterface $container)
    {
        $this->container      = $container;
        $this->tracer         = $container->get(Tracer::class);
        $this->switchManager  = $container->get(SwitchManager::class);
        $this->spanTagManager = $container->get(SpanTagManager::class);
        $this->context        = $container->get(Context::class);
    }


    public function process(ProceedingJoinPoint $proceedingJoinPoint)
    {
        switch ($proceedingJoinPoint->className) {
            case WorkerStartCallback::class:
                return $proceedingJoinPoint->process();
            case DataFormatter::class:
                /************ rpc发送 ***********/
                return $proceedingJoinPoint->process();
            case Client::class:
                /************ rpc返回 ***********/
                $backData = $proceedingJoinPoint->process();
                TraceEntity::getInstance()->setAnRpc($backData);

                return $backData;
            case Producer::class:
                /************ 获取队列信息 ***********/
                return $proceedingJoinPoint->process();
            case TcpServer::class:
                return $proceedingJoinPoint->process();
            case ESClient::class:
                /************ 处理ES追踪链路 ***********/
                return $this->generateEsDebug($proceedingJoinPoint);
            case AbstractServiceClient::class:
                if ($proceedingJoinPoint->methodName === '__generateRpcPath') {
                    return $this->abstractServiceClientGenerateRpcPath($proceedingJoinPoint);
                }

                return $proceedingJoinPoint->process();
            default:
                return $proceedingJoinPoint->process();
        }
    }


    private function generateEsDebug(ProceedingJoinPoint $proceedingJoinPoint): mixed
    {
        $startTime = microtime(true);
        $result    = $proceedingJoinPoint->process();
        /** @var AbstractEndpoint $endpoint */
        $endpoint  = $proceedingJoinPoint->getArguments()[0];
        TraceEntity::getInstance()->setAnEs(
            [
                'time'   => (microtime(true) - $startTime),
                'method' => $endpoint->getMethod(),
                'uri'    => $endpoint->getURI(),
                'params' => $endpoint->getParams(),
                'body'   => json_encode($endpoint->getBody(), 256),
                'option' => $endpoint->getOptions(),
            ]
        );

        return $result;
    }


    private function abstractServiceClientGenerateRpcPath(ProceedingJoinPoint $proceedingJoinPoint): mixed
    {
        $path = $proceedingJoinPoint->process();
        $key  = "JsonRPC send [{$path}]";
        $span = $this->startSpan($key);
        if ($this->spanTagManager->has('rpc', 'path')) {
            $span->setTag($this->spanTagManager->get('rpc', 'path'), $path);
        }
        $carrier = [];
        $this->tracer->inject(
            $span->getContext(),
            TEXT_MAP,
            $carrier
        );
        $this->context->set('tracer.carrier', $carrier);
        $this->context->set('path', $path);
        $this->context->set('time', microtime(true));
        $this->context->set('tracer.span.' . static::class, $span);

        return $path;
    }
}
